﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Maps.MapControl;

namespace SMIL.Services
{
    public class SMILTileSource :  TileSource
    {
        public const int _TILE_SIZE = 256;

        private int _MaxZoomLevel = 0;
        private int _MinZoomLevel = 0;

        private bool _maxZoomLevelSet = false;
        private bool _minZoomLevelSet = false;

        public int MaxZoomLevel
        {
            get { return _MaxZoomLevel; }
            set 
            { 
                _MaxZoomLevel = value;
                _maxZoomLevelSet = true;
            }
        }

        public int MinZoomLevel
        {
            get { return _MinZoomLevel; }
            set 
            { 
                _MinZoomLevel = value;
                _minZoomLevelSet = true;
            }
        }

        public bool MaxZoomLevelSet
        {
            get { return _maxZoomLevelSet; }
        }

        public bool MinZoomLevelSet
        {
            get { return _minZoomLevelSet; }
        }

        /// <summary>
        /// Converts a tile level to a zoom level
        /// </summary>
        /// <param name="tileLevelDetail">The tile level (2^n = pixel width)</param>
        /// <returns>The zoom level</returns>
        public virtual int TileToZoom(int tileLevelDetail)
        {
            return tileLevelDetail - 8;
        }

        /// <summary>
        /// Converts a tile level to a zoom level
        /// </summary>
        /// <param name="zoomLevel">The zoom level</param>
        /// <returns>The tile level (2^n = pixel width)</returns>
        public virtual int ZoomToTile(double zoomLevel)
        {
            return Convert.ToInt32(zoomLevel) + 8;
        }

        /// <summary>
        /// Retrieves a quad key from a Virtual Earth tile specifier URL
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public string GetQuadKey(string url)
        {
            var regex = new Regex(".*tiles/(.+)[.].*");
            Match match = regex.Match(url);

            return match.Groups[1].ToString();
        }

        /// <summary>
        /// Returns the bounding BBox for a grid square represented by the given quad key
        /// </summary>
        /// <param name="quadKey"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="zoomLevel"></param>
        /// <returns></returns>
        public BBox QuadKeyToBBox(string quadKey, int x, int y, int zoomLevel)
        {
            char c = quadKey[0];

            int tileSize = 2 << (18 - zoomLevel - 1);

            if (c == '0')
            {
                y = y - tileSize;
            }

            else if (c == '1')
            {
                y = y - tileSize;
                x = x + tileSize;
            }

            else if (c == '3')
            {
                x = x + tileSize;
            }

            if (quadKey.Length > 1)
            {
                return QuadKeyToBBox(quadKey.Substring(1), x, y, zoomLevel + 1);
            }
            return new BBox(x, y, tileSize, tileSize);
        }

        public BBox QuadKeyToBBox(string quadKey)
        {
            const int x = 0;
            const int y = 262144;
            return QuadKeyToBBox(quadKey, x, y, 1);
        }

        /// <summary>
        /// Converts radians to degrees
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public double RadToDeg(double d)
        {
            return d / Math.PI * 180.0;
        }

        /// <summary>
        /// Converts a grid row to Latitude
        /// </summary>
        /// <param name="y"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public double YToLatitudeAtZoom(int y, int zoom)
        {
            double arc = Constants.EARTH_CIRCUMFERENCE / ((1 << zoom) * _TILE_SIZE);
            double metersY = Constants.HALF_EARTH_CIRCUMFERENCE - (y * arc);
            double a = Math.Exp(metersY * 2 / Constants.EARTH_RADIUS);
            double result = RadToDeg(Math.Asin((a - 1) / (a + 1)));
            return result;
        }

        /// <summary>
        /// Converts a grid column to Longitude
        /// </summary>
        /// <param name="x"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public double XToLongitudeAtZoom(int x, int zoom)
        {
            double arc = Constants.EARTH_CIRCUMFERENCE / ((1 << zoom) * _TILE_SIZE);
            double metersX = (x * arc) - Constants.HALF_EARTH_CIRCUMFERENCE;
            double result = RadToDeg(metersX / Constants.EARTH_RADIUS);
            return result;
        }
        
        public static string TileXYToQuadKey(int tileX, int tileY, int levelOfDetail)
        {
            var quadKey = new StringBuilder();
            for (int i = levelOfDetail; i > 0; i--)
            {
                char digit = '0';
                int mask = 1 << (i - 1);
                if ((tileX & mask) != 0)
                {
                    digit++;
                }
                if ((tileY & mask) != 0)
                {
                    digit++;
                    digit++;
                }
                quadKey.Append(digit);
            }
            return quadKey.ToString();
        }


    }
}
